/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
 *
 */

import * as path from 'path';

import { FileSet, Input, TaskInputValue } from '@ohos/hvigor';
import * as fse from 'fs-extra';

import { parsingProfileName } from '../common/common-util.js';
import { moduleOptionPathInfo } from '../common/module-option-path-info.js';
import { BuildDirConst } from '../const/build-directory-const.js';
import { CommonConst, ValidateRegExp } from '../const/common-const.js';
import { ModuleType } from '../enum/module-type-enum.js';
import { ProjectModel } from '../model/project/project-model.js';
import { TargetSourceSetModel } from '../model/source-set/source-set-model.js';
import { ModuleJson } from '../options/configure/module-json-options.js';
import { AppPreloadHintStartupTasksObj, StartupOptions, StartupTaskObj } from '../options/configure/startup-options.js';
import { Dependency } from '../project/dependency/core/dependency-interface.js';
import { DependencyManager } from '../project/dependency/dependency-manager.js';
import { getLocalDepHarTargetSources, getRemoteDepHarTargetSources } from '../utils/dependency-util.js';
import { getJson5Obj } from '../utils/json-util.js';
import { resModelLoader } from '../utils/loader/file/res-model-loader.js';
import { OhosLogger } from '../utils/log/ohos-logger.js';
import {
  generateOhmUrlForSo,
  generateOhmUrlForSourceFile,
  generateOhmurlForSrcEntry,
  generateOldOhmUrlForSo,
  getOldOhmUrlForSrcEntry
} from '../utils/ohmurl-utils.js';
import { ValidateUtil } from '../utils/validate/validate-util.js';
import { TaskNames } from './common/task-names.js';
import { TargetTaskService } from './service/target-task-service.js';
import { OhosHapTask } from './task/ohos-hap-task.js';

import ModuleOptObj = ModuleJson.ModuleOptObj;

const _log = OhosLogger.getLogger('hvigor-processStartupConfig');

export class ProcessStartupConfig extends OhosHapTask {
  protected readonly targetJsonPath: string;
  private startupMergedOptions: StartupOptions | undefined;

  constructor(targetService: TargetTaskService) {
    super(targetService, TaskNames.Task.PROCESS_STARTUP_CONFIG);
    const targetSourceSetModel: TargetSourceSetModel = targetService.getTargetData().getModuleSourceSetModel() as TargetSourceSetModel;
    const targetRes = targetSourceSetModel.getModuleTargetRes();
    this.targetJsonPath = targetRes.getJsonPath();
  }

  @Input()
  private get useNormalizedOHMUrl(){
    return !!this.targetService.getBuildOption()?.strictMode?.useNormalizedOHMUrl;
  }

  declareInputs(): Map<string, TaskInputValue> {
    const map = super.declareInputs();
    map.set('appStartupFileName', this.appStartupFileName ?? '');
    const currentModuleType = this.moduleModel.getModuleType();
    if (currentModuleType === ModuleType.Har) {
      map.set('isBundledDependencies', this.targetService.isBundledDependencies());
      map.set('isByteCodeHar', this.targetService.isByteCodeHar());
    }
    if (currentModuleType !== ModuleType.Har || this.targetService.isBundledDependencies()) {
      const harDependencies = this.service.getHarDependencies();
      for (let i = 0; i < harDependencies.length; i++) {
        const harDependency = harDependencies[i];
        if (!harDependency.isLocal()) {
          continue;
        }
        const [appStartupProfileName] = this.getLocalHarDepStartupConfig(harDependency);
        map.set(harDependency.getDependencyName(), appStartupProfileName ?? '');
      }
    }
    return map;
  }

  declareInputFiles(): FileSet {
    const fileSet = super.declareInputFiles();
    if (this.appStartupPath && fse.existsSync(this.appStartupPath)) {
      fileSet.addEntry(this.appStartupPath);
    }
    const currentModuleType = this.moduleModel.getModuleType();
    if (currentModuleType !== ModuleType.Har || this.targetService.isBundledDependencies()) {
      const harDependencies = this.service.getHarDependencies();
      for (let i = 0; i < harDependencies.length; i++) {
        const harDependency = harDependencies[i];
        if (!harDependency.isLocal()) {
          continue;
        }
        const [, harStartupFilePath] = this.getLocalHarDepStartupConfig(harDependency);
        if (harStartupFilePath && fse.existsSync(harStartupFilePath)) {
          fileSet.addEntry(harStartupFilePath);
        }
      }
    }
    if (fse.existsSync(this.pathInfo.getIntermediatesPkgContextInfoPath())) {
      fileSet.addEntry(this.pathInfo.getIntermediatesPkgContextInfoPath());
    }
    return fileSet;
  }

  declareOutputFiles(): FileSet {
    return super.declareOutputFiles().addEntry(this.intermediateTempStartupFilePath);
  }

  protected doTaskAction(): void {
    this.appStartupValidate();
    this.checkAppStartupConfig();
  }

  initTaskDepends(): void {
  }

  /**
   * appStartup.json文件schema校验
   * @private
   */
  private appStartupValidate() {
    this.checkResReferenceConfig(CommonConst.APP_STARTUP);
    if (!this.appStartupPath || !fse.existsSync(this.appStartupPath)) {
      return;
    }
    let moduleSchemaPath = this.sdkInfo.getAppStartupSchema();
    const currentModuleType = this.moduleModel.getModuleType();
    if (currentModuleType === ModuleType.Shared) {
      moduleSchemaPath = this.sdkInfo.getHspStartupSchema();
    }
    if (currentModuleType === ModuleType.Har) {
      moduleSchemaPath = this.sdkInfo.getHarStartupSchema();
    }
    ValidateUtil.submitSchemaCheckWork({
      moduleName: this.moduleName,
      filePath: this.appStartupPath,
      schemaPath: moduleSchemaPath,
    });
  }

  /**
   * startup配置文件业务校验
   * startupTasks字段：srcEntry真实存在，且ets/ts/js后缀，name唯一
   * appPreloadHintStartupTask字段：srcEntry后缀为so，name唯一
   * 每个模块（本模块和依赖的har模块）单独判断：srcEntry真实存在，后缀名合规；再合并，合并后判断name是否唯一
   *
   * @private
   */
  private checkAppStartupConfig(): void {
    const appStartupPath = this.appStartupPath;
    const currentModuleType = this.moduleModel.getModuleType();
    const pkgContextInfoPath = this.pathInfo.getIntermediatesPkgContextInfoPath();
    const pkgContextInfos = getJson5Obj(pkgContextInfoPath);
    // 本模块的公共处理
    if (appStartupPath !== undefined && fse.existsSync(appStartupPath)) {
      this.processModuleStartupConfig(appStartupPath, pkgContextInfos, currentModuleType);
    }
    // hap和hsp要合并所有依赖的har的启动框架文件
    if (currentModuleType !== ModuleType.Har || this.targetService.isBundledDependencies()) {
      const harDependencies = this.service.getHarDependencies();
      for (let i = 0; i < harDependencies.length; i++) {
        const harDependency = harDependencies[i];
        if (harDependency.isLocal()) {
          this.processLocalHarDependency(harDependency, pkgContextInfos);
        } else {
          this.processRemoteHarDependency(harDependency, pkgContextInfos);
        }
      }
    }
    if (this.startupMergedOptions === undefined) {
      if (fse.existsSync(this.intermediateTempStartupFilePath)) {
        fse.removeSync(this.intermediateTempStartupFilePath);
      }
      return;
    }
    // 检查启动框架配置中startupTasks字段name唯一性
    this.checkStartupTaskName(this.startupMergedOptions.startupTasks, 'startupTask');
    // 检查启动框架配置中appPreloadHintStartupTasks字段name唯一性
    this.checkStartupTaskName(this.startupMergedOptions.appPreloadHintStartupTasks, 'appPreloadHintStartupTask');
    // 将合并后的数据写入到临时文件
    fse.ensureFileSync(this.intermediateTempStartupFilePath);
    fse.writeFileSync(this.intermediateTempStartupFilePath, JSON.stringify(this.startupMergedOptions));
  }

  /**
   * 校验如"routerMap":"$profile:xxx"等资源引用方式的配置,若对应文件不存在则报错
   * @param configField
   * @private
   */
  private checkResReferenceConfig(configField: string) {
    // 获取$profile:xxx真实文件，判断是否存在
    const moduleJsonObj: ModuleOptObj = resModelLoader.getModuleJson(this.targetJsonPath);
    const checkConfigField: string = moduleJsonObj.module[configField] as string;
    if (!checkConfigField) {
      return;
    }
    const checkConfigFieldProfileName = parsingProfileName(checkConfigField);
    if (!checkConfigFieldProfileName) {
      return;
    }
    const moduleFilePath = moduleOptionPathInfo.getModuleOptPath(this.targetJsonPath, configField);
    const filePath = this.targetService.getTargetResourceBaseProfilePath(`${checkConfigFieldProfileName}.json`);
    if (!filePath || !fse.existsSync(filePath)) {
      _log.printErrorExit(
        'STARTUP_CONFIG_FILE_NOT_EXIST',
        [this.moduleName, moduleFilePath],
        [[filePath]]
      );
    }
  }

  /**
   * 处理本地依赖har
   * @param harDependency
   * @param pkgContextInfos
   * @private
   */
  private processLocalHarDependency(harDependency: Dependency, pkgContextInfos: any) {
    const harDependencyModule = DependencyManager.getModuleModelByDependency(harDependency, this.projectModel);
    if (harDependencyModule === undefined) {
      return;
    }
    const harDependencyModuleName = harDependencyModule.getModule().getName();
    const [appStartupProfileName, harStartupFilePath] = this.getLocalHarDepStartupConfig(harDependency);
    if (!appStartupProfileName || !harStartupFilePath) {
      return;
    }
    const harAppStartupConfigOpt: StartupOptions = getJson5Obj(harStartupFilePath);
    // 对harAppStartupConfigOpt做srcEntry属性真实性存在校验和ets/ts/js结尾校验
    if (harAppStartupConfigOpt.startupTasks !== undefined) {
      harAppStartupConfigOpt.startupTasks.forEach((harStartupTask: StartupTaskObj) => {
        this.checkStartupPath(harStartupFilePath, harStartupTask.srcEntry, harDependencyModule?.getModule().getNodeDir(), harDependencyModuleName);
        harStartupTask.srcEntryAbsolutePath = path.resolve(harDependencyModule?.getModule().getNodeDir(), CommonConst.SRC_MAIN, harStartupTask.srcEntry);
        harStartupTask.moduleName = harDependencyModuleName;
        harStartupTask.startupConfigPath = harStartupFilePath;
        harStartupTask.ohmurl = generateOhmurlForSrcEntry(this.targetService, this.moduleName, harDependency, harStartupTask.srcEntry);
      });
    }
    // 检查appPreloadHintStartupTasks中srcEntry属性值以so结尾
    if (harAppStartupConfigOpt.appPreloadHintStartupTasks !== undefined) {
      harAppStartupConfigOpt.appPreloadHintStartupTasks.forEach((appPreloadHintStartupTask: AppPreloadHintStartupTasksObj) => {
        this.checkAppPreloadHintStartupTaskPath(harStartupFilePath, appPreloadHintStartupTask.srcEntry, harDependencyModuleName, pkgContextInfos);
        appPreloadHintStartupTask.moduleName = harDependencyModuleName;
        appPreloadHintStartupTask.startupConfigPath = harStartupFilePath;
        const srcEntryBaseName = path.basename(appPreloadHintStartupTask.srcEntry);
        appPreloadHintStartupTask.ohmurl = this.useNormalizedOHMUrl
          ? generateOhmUrlForSo(pkgContextInfos[srcEntryBaseName] ?? {}, appPreloadHintStartupTask.srcEntry)
          : generateOldOhmUrlForSo(this.targetService, appPreloadHintStartupTask.srcEntry, this.moduleName);
      });
    }
    if (this.startupMergedOptions === undefined) {
      this.startupMergedOptions = harAppStartupConfigOpt;
    } else {
      this.startupMergedOptions.startupTasks = [...(this.startupMergedOptions.startupTasks || []), ...(harAppStartupConfigOpt.startupTasks || [])];
      this.startupMergedOptions.appPreloadHintStartupTasks = [
        ...(this.startupMergedOptions.appPreloadHintStartupTasks || []),
        ...(harAppStartupConfigOpt.appPreloadHintStartupTasks || []),
      ];
    }
  }

  /**
   * 得到依赖模块本地har的启动框架文件名和启动框架配置路径
   *
   * @param harDependency
   * @private
   */
  private getLocalHarDepStartupConfig(harDependency: Dependency): [string | undefined, string | undefined] {
    const harDependencyModule = DependencyManager.getModuleModelByDependency(harDependency, this.projectModel);
    if (harDependencyModule === undefined) {
      return [undefined, undefined];
    }
    const harModuleJsonObj = harDependency.getModuleJsonObj();
    if (harModuleJsonObj === undefined) {
      return [undefined, undefined];
    }
    const appStartup: string | undefined = harModuleJsonObj.module.appStartup;
    if (appStartup === undefined) {
      return [undefined, undefined];
    }
    const appStartupProfileName = parsingProfileName(appStartup);
    if (appStartupProfileName === undefined) {
      return [undefined, undefined];
    }
    const harStartupFilePath =
      this.getLocalHarDepStartupFile(this.targetService, harDependency, this.projectModel, appStartupProfileName);
    return [appStartupProfileName, harStartupFilePath];
  }

  /**
   * 得到本地依赖har的资源下的启动框架配置文件路径（适配多target场景）
   *
   * @private
   */
  private getLocalHarDepStartupFile(targetTaskService: TargetTaskService, harDependency: Dependency, projectModule: ProjectModel,
    appStartupProfileName: string) {
    const harTargetSources = getLocalDepHarTargetSources(targetTaskService, harDependency, projectModule);
    for (const harTargetSource of harTargetSources) {
      const harStartupFilePath = path.resolve(harTargetSource, CommonConst.BASE_PROFILE, `${appStartupProfileName}.json`);
      if (fse.existsSync(harStartupFilePath)) {
        return harStartupFilePath;
      }
    }
    return undefined;
  }

  /**
   * 处理远程依赖的har的startup，远程har基础校验不用做，在打包的时候做过了
   * @private
   * @param remoteDependency
   * @param pkgContextInfos
   */
  private processRemoteHarDependency(remoteDependency: Dependency, pkgContextInfos: any) {
    const remoteDependencySrcMainPath = path.resolve(remoteDependency.getDependencyRootPath(), BuildDirConst.SRC, BuildDirConst.MAIN);
    const moduleJsonObj: ModuleOptObj = DependencyManager.getModuleObjByRemoteDependency(remoteDependency);
    const startupFileName = parsingProfileName(moduleJsonObj.module.appStartup) ?? undefined;
    if (!startupFileName) {
      return;
    }
    const startupFilePath = this.getRemoteHarDepStartupFile(remoteDependency, startupFileName);
    if (!startupFilePath) {
      return;
    }
    const startupOptions: StartupOptions = getJson5Obj(startupFilePath);
    startupOptions.startupTasks?.forEach((startupTask) => {
      startupTask.srcEntryAbsolutePath = path.resolve(remoteDependencySrcMainPath, startupTask.srcEntry);
      startupTask.moduleName = moduleJsonObj.module.name;
      startupTask.startupConfigPath = startupFilePath;
      startupTask.ohmurl = generateOhmurlForSrcEntry(this.targetService, this.moduleName, remoteDependency, startupTask.srcEntry);
    });
    startupOptions.appPreloadHintStartupTasks?.forEach((appPreloadHintStartupTask) => {
      appPreloadHintStartupTask.moduleName = moduleJsonObj.module.name;
      appPreloadHintStartupTask.startupConfigPath = startupFilePath;
      appPreloadHintStartupTask.isRemoteByteCodeHar = remoteDependency.isByteCodeHarDependency();
      const srcEntryBaseName = path.basename(appPreloadHintStartupTask.srcEntry);
      appPreloadHintStartupTask.ohmurl = this.useNormalizedOHMUrl
        ? generateOhmUrlForSo(pkgContextInfos[srcEntryBaseName] ?? {}, appPreloadHintStartupTask.srcEntry)
        : generateOldOhmUrlForSo(this.targetService, appPreloadHintStartupTask.srcEntry, this.moduleName);
    });
    if (this.startupMergedOptions === undefined) {
      this.startupMergedOptions = startupOptions;
    } else {
      this.startupMergedOptions.startupTasks = [...(this.startupMergedOptions.startupTasks || []), ...(startupOptions.startupTasks || [])];
      this.startupMergedOptions.appPreloadHintStartupTasks = [
        ...(this.startupMergedOptions.appPreloadHintStartupTasks || []),
        ...(startupOptions.appPreloadHintStartupTasks || []),
      ];
    }
  }

  /**
   * 获取远程依赖har（产物态）的启动框架配置文件（适配多target场景）
   * @param harDependency
   * @param appStartupProfileName
   * @private
   */
  private getRemoteHarDepStartupFile(harDependency: Dependency, appStartupProfileName: string) {
    const remoteHarSources = getRemoteDepHarTargetSources(harDependency);
    for (const remoteHarSource of remoteHarSources) {
      const harStartupFilePath = path.resolve(remoteHarSource, CommonConst.BASE_PROFILE, `${appStartupProfileName}.json`);
      if (fse.existsSync(harStartupFilePath)) {
        return harStartupFilePath;
      }
    }
    return undefined;
  }

  /**
   * 处理本模块的startup配置文件校验
   * @private
   */
  private processModuleStartupConfig(appStartupPath: string, pkgContextInfos: any, currentModuleType: ModuleType) {
    const appStartupConfigOpt = getJson5Obj(appStartupPath);
    // 检查startupTasks中srcEntry属性值真实存在且以ets/ts/js结尾
    if (appStartupConfigOpt?.startupTasks !== undefined) {
      appStartupConfigOpt.startupTasks.forEach((startupTask: StartupTaskObj) => {
        this.checkStartupPath(appStartupPath, startupTask.srcEntry, this.moduleModel.getModule().getNodeDir(),
          this.moduleName);
        const moduleSrcMain = path.resolve(this.module.getNodeDir(), BuildDirConst.SRC, BuildDirConst.MAIN);
        startupTask.srcEntryAbsolutePath = path.resolve(moduleSrcMain, startupTask.srcEntry);
        startupTask.moduleName = this.moduleName;
        startupTask.startupConfigPath = appStartupPath;
        if (this.targetService.isByteCodeHar() || currentModuleType !== ModuleType.Har) {
          startupTask.ohmurl = this.useNormalizedOHMUrl
            ? generateOhmUrlForSourceFile(pkgContextInfos[this.packageJsonObj.name] ?? {},
              path.join(BuildDirConst.SRC, BuildDirConst.MAIN, startupTask.srcEntry))
            : getOldOhmUrlForSrcEntry(this.targetService, startupTask.srcEntry, this.moduleName);
        }
      });
    }
    if (appStartupConfigOpt?.appPreloadHintStartupTasks !== undefined) {
      // 检查appPreloadHintStartupTasks中srcEntry属性值以so结尾
      appStartupConfigOpt.appPreloadHintStartupTasks.forEach((appPreloadHintStartupTask: AppPreloadHintStartupTasksObj) => {
        this.checkAppPreloadHintStartupTaskPath(appStartupPath, appPreloadHintStartupTask.srcEntry, this.moduleName, pkgContextInfos);
        appPreloadHintStartupTask.moduleName = this.moduleName;
        appPreloadHintStartupTask.startupConfigPath = appStartupPath;
        if (this.targetService.isByteCodeHar() || currentModuleType !== ModuleType.Har) {
          const srcEntryBaseName = path.basename(appPreloadHintStartupTask.srcEntry);
          appPreloadHintStartupTask.ohmurl = this.useNormalizedOHMUrl
            ? generateOhmUrlForSo(pkgContextInfos[srcEntryBaseName] ?? {}, appPreloadHintStartupTask.srcEntry)
            : generateOldOhmUrlForSo(this.targetService, appPreloadHintStartupTask.srcEntry, this.moduleName);
        }
      });
    }
    // 检查startupTasks中configEntry属性值真实存在且以ets/ts/js结尾
    if (appStartupConfigOpt?.configEntry !== undefined) {
      this.checkStartupPath(appStartupPath, appStartupConfigOpt.configEntry, this.moduleModel.getModule().getNodeDir(), this.moduleName);
    }
    if (appStartupConfigOpt !== undefined) {
      this.startupMergedOptions = appStartupConfigOpt;
    }
  }

  /**
   * 1.srcEntry校验
   * 2.configEntry校验
   * srcEntry不能是绝对路径；必须是本模块文件；文件必须真实存在;指向文件必须以ets,ts,js后缀结尾
   * @param appStartupPath
   * @param srcEntryPath
   * @param moduleModelPath
   * @param moduleName
   * @private
   */
  private checkStartupPath(appStartupPath: string, srcEntryPath: string | undefined, moduleModelPath: string, moduleName: string): void {
    if (srcEntryPath === undefined) {
      return;
    }
    const absoluteSrcEntryPath = path.resolve(moduleModelPath, BuildDirConst.SRC, BuildDirConst.MAIN, srcEntryPath);
    if (path.isAbsolute(srcEntryPath)) {
      _log.printErrorExit(
        'STARTUP_SRCENTRY_IS_ABSOLUTE',
        [srcEntryPath, moduleName, appStartupPath],
        []
      );
    }
    if (!ValidateRegExp.STARTUP_TASK_SRC_ENTRY_REG_EXP.test(path.extname(absoluteSrcEntryPath))) {
      _log.printErrorExit(
        'STARTUP_SRCENTRY_SUFFIX',
        [srcEntryPath, moduleName, appStartupPath],
        []
      );
    }
    if (!absoluteSrcEntryPath.startsWith(moduleModelPath)) {
      _log.printErrorExit(
        'STARTUP_SRCENTRY_MODULE_FILE',
        [srcEntryPath, moduleName, appStartupPath],
        []
      );
    }
    if (!fse.existsSync(absoluteSrcEntryPath)) {
      _log.printErrorExit(
        'STARTUP_SRCENTRY_FILE_EXIST',
        [srcEntryPath, moduleName, appStartupPath],
        []
      );
    }
  }

  /**
   * appPreloadHintStartupTask中srcEntry值的业务校验
   *  1 校验srcEntry的值是否是so后缀结尾
   *  2 校验srcEntry的值在信息语义表pkgContextInfo.json中是否存在
   *
   * @param appStartupPath
   * @param appPreloadHintStartupTaskPath
   * @param moduleName
   * @param pkgContextInfos
   * @private
   */
  private checkAppPreloadHintStartupTaskPath(appStartupPath: string, appPreloadHintStartupTaskPath: string, moduleName: string, pkgContextInfos: any) {
    // 检查srcEntry属性值以so后缀结尾
    if (!ValidateRegExp.PRELOAD_STARTUP_TASK_SRC_ENTRY_REG_EXP.test(appPreloadHintStartupTaskPath)) {
      _log.printErrorExit(
        'APP_PRELOAD_STARTUP_SRCENTRY_SUFFIX',
        [appPreloadHintStartupTaskPath, moduleName, appStartupPath],
        []
      );
    }
    // 如果useNormalizedOHMUrl=true并且语义信息表存在，则可以使用语义信息表来检测so是否存在
    if (this.useNormalizedOHMUrl && !!pkgContextInfos) {
      const pkgContextInfoKeys = Object.keys(pkgContextInfos);
      if (!pkgContextInfoKeys.includes(path.basename(appPreloadHintStartupTaskPath))) {
        _log.printErrorExit(
          'STARTUP_SO_FILE_EXIST',
          [appPreloadHintStartupTaskPath, moduleName, appStartupPath],
          [[appPreloadHintStartupTaskPath]]
        );
      }
    }
  }

  /**
   * 校验启动框架配置文件中startupTasks和appPreloadHintStartupTasks任务对象集合中对象名称唯一
   * @private
   * @param startupTaskObjList
   * @param startTaskType
   */
  private checkStartupTaskName(startupTaskObjList: StartupTaskObj[] | AppPreloadHintStartupTasksObj[] | undefined, startTaskType: string): void {
    if (!startupTaskObjList || startupTaskObjList.length <= 1) {
      return;
    }
    const startupTaskObjSet = new Set();
    for (let i = 0; i < startupTaskObjList.length; i++) {
      const curStartupTaskName = startupTaskObjList[i].name;
      if (startupTaskObjSet.has(curStartupTaskName)) {
        _log.printErrorExit(
          'STARTUP_TASK_NAME_UNIQUE',
          [curStartupTaskName, startTaskType, startupTaskObjList[i].moduleName],
          [[startTaskType, startupTaskObjList[i].moduleName, curStartupTaskName]]
        );
      } else {
        startupTaskObjSet.add(curStartupTaskName);
      }
    }
  }
}
